﻿using System.Configuration;
using System.Runtime.Remoting.Messaging;
using System.Web;
using HiTeachers.Utilities;
using NHibernate;
using NHibernate.Cache;

namespace HiTeachers.Data.Manager
{
    public class NHibernateSessionManager
    {
        #region Thread-safe, lazy Singleton

        /// <summary>
        /// This is a thread-safe, lazy singleton.  See http://www.yoda.arachsys.com/csharp/singleton.html
        /// for more details about its implementation.
        /// </summary>
        public static NHibernateSessionManager Instance
        {
            get
            {
                return Nested.NHibernateSessionManager;
            }
        }

        /// <summary>
        /// Initializes the NHibernate session factory upon instantiation.
        /// </summary>
        private NHibernateSessionManager()
        {
            InitSessionFactory();
        }

        /// <summary>
        /// Assists with ensuring thread-safe, lazy singleton
        /// </summary>
        internal class Nested
        {
            static Nested() { }
            internal static readonly NHibernateSessionManager NHibernateSessionManager = new NHibernateSessionManager();
        }

        //internal class NestedImpl : Nested
        //{
        //}

        #endregion

        private void InitSessionFactory()
        {
            //_sessionFactory = new NHibernate.Cfg.Configuration().Configure().BuildSessionFactory();

            //NHibernate.Cfg.Environment.BytecodeProvider = null;
            //NHibernate.Cfg.Environment.UseReflectionOptimizer = false;
            NHibernate.Cfg.Configuration cfg = new NHibernate.Cfg.Configuration();

            // The following makes sure the the web.config contains a declaration for the HBM_ASSEMBLY appSetting
            if (ConfigurationManager.AppSettings["HBM_ASSEMBLY"] == null ||
                ConfigurationManager.AppSettings["HBM_ASSEMBLY"] == "")
            {
                throw new ConfigurationErrorsException("NHibernateManager.InitSessionFactory: \"HBM_ASSEMBLY\" must be " +
                    "provided as an appSetting within your config file. \"HBM_ASSEMBLY\" informs NHibernate which assembly " +
                    "contains the HBM files. It is assumed that the HBM files are embedded resources. An example config " +
                    "declaration is <add key=\"HBM_ASSEMBLY\" value=\"MyProject.Core\" />");
            }

            cfg.AddAssembly(System.Configuration.ConfigurationManager.AppSettings["HBM_ASSEMBLY"]);
            _sessionFactory = cfg.BuildSessionFactory();
        }

        /// <summary>
        /// Allows you to register an interceptor on a new session.  This may not be called if there is already
        /// an open session attached to the HttpContext.  If you have an interceptor to be used, modify
        /// the HttpModule to call this before calling BeginTransaction().
        /// </summary>
        public void RegisterInterceptor(IInterceptor interceptor)
        {
            ISession session = ContextSession;

            if (session != null && session.IsOpen)
            {
                throw new CacheException("You cannot register an interceptor once a session has already been opened");
            }

            GetSession(interceptor);
        }

        public ISession CreateSession()
        {
            return _sessionFactory.OpenSession();
        }

        public ISession GetSession()
        {
            return GetSession(null);
        }

        /// <summary>
        /// Gets a session with or without an interceptor.  This method is not called directly; instead,
        /// it gets invoked from other public methods.
        /// </summary>
        private ISession GetSession(IInterceptor interceptor)
        {
            ISession session = ContextSession;

            if (session == null)
            {
                session = interceptor != null ? _sessionFactory.OpenSession(interceptor) : _sessionFactory.OpenSession();

                ContextSession = session;
            }

            Check.Ensure(session != null, "session was null");

            return session;
        }

        /// <summary>
        /// Flushes anything left in the session and closes the connection.
        /// </summary>
        public void CloseSession()
        {
            ISession session = ContextSession;

            if (session != null && session.IsOpen)
            {
                session.Flush();
                session.Close();
            }

            ContextSession = null;
        }

        public void BeginTransaction()
        {
            //ITransaction transaction = GetSession().Transaction;
            //if (transaction != null && transaction.IsActive)
            //{
            //    throw new System.Data.DataException("Not support concurrent transaction");
            //}
            //if (transaction != null) transaction.Begin();


            //GetSession().Transaction.Begin();

            ITransaction transaction = ContextTransaction;

            if (transaction == null)
            {
                transaction = GetSession().BeginTransaction();
                ContextTransaction = transaction;
            }
        }

        public void CommitTransaction()
        {
            ISession session = GetSession();
            ITransaction transaction = session.Transaction;
            if (transaction != null && transaction.IsActive)
            {
                try
                {
                    session.Flush();
                    transaction.Commit();
                }
                catch
                {
                    transaction.Rollback();
                    throw;
                }
            }
        }

        public bool HasOpenTransaction()
        {
            //ITransaction transaction = GetSession().Transaction;

            ITransaction transaction = ContextTransaction;

            return transaction != null && !transaction.WasCommitted && !transaction.WasRolledBack;
        }

        public void RollbackTransaction()
        {
            ITransaction transaction = GetSession().Transaction;
            if (transaction != null && transaction.IsActive)
                transaction.Rollback();
        }

        /// <summary>
        /// If within a web context, this uses <see cref="HttpContext" /> instead of the WinForms 
        /// specific <see cref="CallContext" />.  Discussion concerning this found at 
        /// http://forum.springframework.net/showthread.php?t=572.
        /// </summary>
        private ITransaction ContextTransaction
        {
            get
            {
                //if (IsInWebContext())
                //{
                    return (ITransaction)HttpContext.Current.Items[TransactionKey];
                //}
                //return (ITransaction)CallContext.GetData(TransactionKey);
            }
            set
            {
                //if (IsInWebContext())
                //{
                    HttpContext.Current.Items[TransactionKey] = value;
                //}
                //else
                //{
                //    CallContext.SetData(TransactionKey, value);
                //}
            }
        }

        /// <summary>
        /// If within a web context, this uses <see cref="HttpContext" /> instead of the WinForms 
        /// specific <see cref="CallContext" />.  Discussion concerning this found at 
        /// http://forum.springframework.net/showthread.php?t=572.
        /// </summary>
        private static ISession ContextSession
        {
            //get
            //{
            //    if (IsInWebContext())
            //    {
            //        return (ISession)HttpContext.Current.Items[SessionKey];
            //    }
            //    return (ISession)CallContext.GetData(SessionKey);
            //}
            //set
            //{
            //    if (IsInWebContext())
            //    {
            //        HttpContext.Current.Items[SessionKey] = value;
            //    }
            //    else
            //    {
            //        CallContext.SetData(SessionKey, value);
            //    }
            //}



            get { return (ISession)HttpContext.Current.Items[SessionKey]; }
            set { HttpContext.Current.Items[SessionKey] = value; }
        }

        private static bool IsInWebContext()
        {
            return HttpContext.Current != null;
        }

        private const string TransactionKey = "CONTEXT_TRANSACTION";
        private const string SessionKey = "CONTEXT_SESSION";
        private ISessionFactory _sessionFactory;
    }
}
